import { Advanced, UiFrameworkExtension } from '../../components'
import { Link, RepoLink } from '@brillout/docpress'
import { ConfigSpec } from '../../components'

<ConfigSpec
  env="config (development & build-time)"
  global={null}
>
```ts ts-only hide-menu
type Meta = {
  env: {
    client?: boolean
    server?: boolean
    config?: boolean
  }
  cumulative?: boolean
  global?: boolean | ((value: unknown, info: { isGlobalLocation: boolean }) => boolean)
  eager?: boolean
  effect?: ((config: { configValue: unknown configDefinedAt: string }) => Config | undefined)
  isDefinedByPeerDependency?: true
}
```
</ConfigSpec>

<Advanced>
  The `meta` setting is an advanced feature. Instead of using `meta`, consider using <Link href="/file-env" noBreadcrumb={true} /> or <Link href="/extensions">Vike Extensions</Link> (they use `meta` on your behalf).
</Advanced>

The `meta` settings enables you to create:
 - **New settings**.
   - <Link href="#example-dataendpointurl" doNotInferSectionTitle />
   - <Link href="#example-sql" doNotInferSectionTitle />
   - <Link href="#example-title-and-description" doNotInferSectionTitle />
   - <Link href="#example-layout" doNotInferSectionTitle />
 - **New hooks**.
   - Example: `+onInit` or `+onBeforeFetchingData`
 - **Modifying the environment of existing hooks**.
   - <Link href="#example-modify-data-env" doNotInferSectionTitle />


## Example: `+dataEndpointUrl`

This example showcases a simple data fetching logic that uses URL data endpoints (e.g. a RESTful API).

```ts
// /pages/countries/+dataEndpointUrl.ts
// Environment: server

export default 'https://restcountries.com/v3.1/all'
```

```ts
// /pages/+config.ts
// Environment: config

import type { Config } from 'vike/types'

export default {
  meta: {
    dataEndpointUrl: {
      env: {
        server: true,
        // Load the value of /pages/**/+dataEndpointUrl.ts only on the server
        client: false
      }
    }
  }
} satisfies Config
```

```ts
// /pages/+data.ts
// Environment: server

export { data }
export type Data = Awaited<ReturnType<typeof data>>

import type { PageContextServer } from 'vike/types'
import fetch from 'node-fetch'

async function data(pageContext: PageContextServer) {
  // The value exported by /pages/countries/+dataEndpointUrl.ts is
  // available at pageContext.config.dataEndpointUrl
  const response = await fetch(pageContext.config.dataEndpointUrl)
  // ...
}
```

## Example: `+sql`

Similarly to the previous example, another common use case is to enable pages to define their data requirements as SQL queries.

```ts
// /pages/product/@id/+sql.ts
export const sql = (id) => `SELECT { name, price } FROM products WHERE id = "${id}";`
/* Or with an ORM:
export const sql = { modelName: 'Product', select: ['name', 'price'], where: { id } } */
```

```ts
// /pages/user/@id/+sql.ts
export const sql = (id) => `SELECT { firstName, lastName } FROM users WHERE id = "${id}";`
/* Or with an ORM:
export const sql = { modelName: 'User', select: ['firstName', 'lastName'], where: { id } } */
```

```ts
// /pages/+config.ts
// Environment: config

import type { Config } from 'vike/types'

export default {
  meta: {
    sql: {
      env: {
        server: true,
        // Load the value of /pages/**/+sql.ts only on the server
        client: false
      }
    }
  }
} satisfies Config
```

```ts
// /pages/+onBeforeRender.ts
// Environment: server

export { onBeforeRender }

import type { PageContextServer } from 'vike/types'
import { runQuery } from 'some-sql-engine'

async function onBeforeRender(pageContext: PageContextServer) {
  // The value exported by /pages/**/+sql.ts is available at pageContext.config.sql
  const { id } = pageContext.routeParams
  const { sql } = pageContext.config
  const query = sql(id)
  const data = await runQuery(query)
  // ...
}
```


## Example: `+title` and `+description`

In order to define the tags `<title>` and `<meta name="description">` of a page's HTML, you can create new settings `title` and `description`.

> If you use a <UiFrameworkExtension />, then you don't need to create the `title` and `description` settings as they are already created by <UiFrameworkExtension name noLink />. You can still read this section as it's a good example for showcasing `meta`.

We first show how the settings `title` and `description` are used and then their implementation.

#### Usage

```ts
// /pages/about-us/+title.ts

export default 'About Us'
```

```ts
// /pages/about-us/+description.ts

export default 'Who we are, our values, and what we stand for.'
```

> You can also use `+config.js` instead of creating the files `+title.js` and `+description.js`:
>
> ```ts
> // /pages/about/+config.ts
>
> import type { Config } from 'vike/types'
>
> export default {
>   title: 'About Us',
>   description: 'Who we are, our values, and what we stand for.'
> } satisfies Config
> ```
>
> See <Link href="/config" />.

Your pages can also use data that was dynamically fetched in order to determine `<title>` and `<meta name="description">`:

```ts
// /pages/product/+title.ts
// Environment: server & client

import type { PageContext } from 'vike/types'
import type { Data } from './+data'

export default (pageContext: PageContext<Data>) => pageContext.data.product.name
```
```ts
// /pages/product/+description.ts
// Environment: server

import type { PageContextServer } from 'vike/types'
import type { Data } from './+data'

export default (pageContext: PageContextServer<Data>) => {
  const { product } = pageContext.data
  return `${product.name} - ${product.description}`
}
```

#### Implementation

```ts
// /pages/+config.ts

import type { Config } from 'vike/types'

export default {
  meta: {
    title: {
      // Make the value of `title` available on both the server- and client-side
      env: { server: true, client: true }
    },
    description: {
      // Make the value of `description` available only on the server-side
      env: { server: true }
    }
  }
} satisfies Config
```

```ts
// /pages/+onRenderClient.ts
// Environment: client

export { onRenderClient }

import type { PageContextClient } from 'vike/types'
import { getTitle } from './utils'

function onRenderClient(pageContext: PageContextClient) {
  // Update the value of <title> upon page navigation
  if (!pageContext.isHydration) {
    document.title = getTitle(pageContext)
  }
  // ...
}
```


```ts
// /pages/utils.ts
// Environment: server & client

import type { PageContext } from 'vike/types'
export { getTitle, getDescription }

function getTitle(pageContext: PageContext) {
  // The value exported by /pages/**/+title.ts is available at pageContext.config.title
  const val = pageContext.config.title
  if (!val) return 'Some default title'
  if (typeof val === 'string') return val
  if (typeof val === 'function') return val(pageContext)
}
function getDescription(pageContext: PageContext) {
  // Same as getTitle()
  // ...
}
```

```tsx
// /pages/+onRenderHtml.ts
// Environment: server

export { onRenderHtml }

import type { PageContextServer } from 'vike/types'
import { getTitle, getDescription } from './utils'
import { escapeInject, dangerouslySkipEscape } from 'vike/server'
import { renderToHtml } from 'some-ui-framework'

async function onRenderHtml(pageContext: PageContextServer) {
  const title = getTitle(pageContext)
  const description = getDescription(pageContext)
  return escapeInject`<!DOCTYPE html>
    <html>
      <head>
        <title>${title}</title>
        <meta name="description" content="${description}" />
      </head>
      <body>
        <div id="root">${dataEndpointUrl(renderToHtml(<Page {...pageContext.data} />))}</div>
      </body>
    </html>`
}
```

> This implementation can be improved: the file `/pages/utils.js` is loaded on both the server- and client-side but `getDescription()` is only used by the server-side. This means `getDescription()` unnecessarily bloats the size of your client-side bundles. It would be cleaner to split `/pages/utils.js` in two separate files: one for the client-side and another one for the server-side. In general, be extra careful in which environment each file is loaded; consider using <Link href="/file-env">`.server.js` and `.client.js`</Link> to ensure the environment of files.


## Example: `+Layout`

Another common use case for `meta` is to create a component `<Layout>` that defines the layout of the page.

```ts
// /pages/product/+Layout.ts
// Environment: client and server

export { Layout } from '../layouts/Responsive'
```

```ts
// /pages/admin/+Layout.ts
// Environment: client and server

export { Layout } from '../layouts/Dashboard'
```

```ts
// /pages/+Layout.ts
// Environment: client and server

// The default layout in case the page doesn't set one
export { Layout } from '../layouts/LayoutDefault'
```

```ts
// /pages/+config.ts
// Environment: config

import type { Config } from 'vike/types'

export default {
  meta: {
    Layout: {
      // Load the value of /pages/**/+Layout.ts on both the server and client
      env: { server: true, client: true },
      // Make it cumulative for nested layouts
      cumulative: true
    }
  }
} satisfies Config
```

```tsx
// /pages/+onRenderHtml.ts
// Environment: server

export { onRenderHtml }

import type { PageContextServer } from 'vike/types'
import { renderToHtml } from 'some-ui-framework'
import { escapeInject, dangerouslySkipEscape } from 'vike/server'

async function onRenderHtml(pageContext: PageContextServer) {
  const { Page, Layout } = pageContext.config
  const pageHtml = renderToHtml(<Layout><Page/></Layout>)
  return escapeInject`<!DOCTYPE html>
    <html>
      <body>
        <div id="root">${dangerouslySkipEscape(pageHtml)}</div>
      </body>
    </html>`
}
```

> Note how we use `pageContext.config.Page` instead of `pageContext.Page`: that's because `pageContext.Page` is just an alias for `pageContext.config.Page`.

```tsx
// /pages/+onRenderClient.tsx
// Environment: client

export { onRenderClient }

import type { PageContextClient } from 'vike/types'
import { renderDom } from 'some-ui-framework'

async function onRenderClient(pageContext: PageContextClient) {
  const { Page, Layout } = pageContext.config
  renderDom(
    <Layout>
      <Page />
    </Layout>,
    document.getElementById('root')
  )
}
```


## Example: modify `+data` env

You can use `meta` to change the environment in which built-in hooks are loaded. For example, you can change the environment of the <Link href="/data">`data()` hook</Link> from `{ server: true, client: false }` to `{ server: true, client: true }`.

```ts
// /pages/some-page/+config.ts

import type { Config } from 'vike/types'

export default {
  meta: {
    data: {
      // By default, the data() hook is loaded and executed only on the
      // server-side. By using meta we can make it isomorphic instead:
      // data() is loaded and executed as well on the client-side.
      env: { server: true, client: true }
    }
  }
} satisfies Config
```

> Making `data()` isomorphic allows you to fetch data directly between the user's browser and the data source (without involving your server), see <Link href="/data#environment" />.

If you want to change the `meta.env` on a page-by-page basis, you can use the the <Link href="/file-env">file extension `.shared.js`</Link> (renaming `+data.js` to `+data.shared.js`), or you can create a new `dataIsomorph` config:

```ts
// /pages/+config.ts

export { config }

import type { Config, ConfigEffect } from 'vike/types'

const config = {
  meta: {
    dataIsomorph: {
      env: { config: true },
      effect
    }
  }
} satisfies Config

const effect: ConfigEffect = ({ configDefinedAt, configValue }) => {
  if (typeof configValue !== 'boolean') {
    throw new Error(`${configDefinedAt} should be a boolean`)
  }
  if (configValue === true) {
    return {
      meta: {
        data: {
          env: { server: true, client: true }
        }
      }
    }
  }
}
```

Example:
 - [/examples/react-full/](https://github.com/vikejs/vike/blob/43503a4829e4bb0171785bc8fa501c7ca096a150/examples/react-full/)
   - Toggle definition: [/renderer/+config.ts > `meta.dataIsomorph`](https://github.com/vikejs/vike/blob/43503a4829e4bb0171785bc8fa501c7ca096a150/examples/react-full/renderer/%2Bconfig.ts#L15-L33)
   - Toggle usage: [/pages/star-wars/@id/+dataIsomorph.ts](https://github.com/vikejs/vike/blob/43503a4829e4bb0171785bc8fa501c7ca096a150/examples/react-full/pages/star-wars/@id/+dataIsomorph.ts)
   - Hook: [/pages/star-wars/@id/+data.tsx](https://github.com/vikejs/vike/blob/43503a4829e4bb0171785bc8fa501c7ca096a150/examples/react-full/pages/star-wars/@id/+data.tsx)


## API

```ts
// /pages/+config.ts
// Environment: config

import type { Config } from 'vike/types'

export default {
  meta: {
    someSettingOrHook: {
      // [Required] Defines the environment in which the config value is loaded.
      env: {
        // Load the config value on the server. [Required]
        server: true,
        // Load the config value on the client. [Required]
        client: true,
        // Load the config value at config-time (when Vike loads +config.ts files). [Optional]
        config: false // default value
      },

      // [Optional] Whether config values should be merged (instead of overridden).
      cumulative: false, // default value

      // [Optional] Function called when the config value is loaded at config-time.
      // Requires `env.config` to be `true`.
      effect({ configDefinedAt, configValue }) {
        if (someCondition) {
          // This config object is merged with the current one.
          return {
            meta: {
              // This can be the same or another hook/setting.
              someOtherSettingOrHook: {
                env: { server: true, client: false }
              }
            }
          }
        }
      }
    }
  }
} satisfies Config
```


## TypeScript

Similar to <Link href="/pageContext#typescript">`Vike.PageContext`</Link>, you can extend (or refine) Vike's `Config` type by using the global interface `Vike.Config`.

```ts ts-only
// /pages/+config.ts
// Environment: config

import type { Config } from 'vike/types'

export default {
  meta: {
    sqlQuery: {
      env: { server: true }
    }
  }
} satisfies Config

declare global {
  namespace Vike {
    interface Config {
      /** The SQL query that retrieves the page's data */
      sqlQuery?: (id: number) => string
    }
  }
}

// The following isn't needed in this example, but if you define Vike.Config
// in a .d.ts file then make sure there is at least one export/import statement.
// Tell TypeScript this file isn't an ambient module:
export {}
```

```ts ts-only
// /pages/product/@id/+config.ts

import { Config } from 'vike/types'

export default {
  // ✅ Typed
  sqlQuery: (id) => `SELECT { name, price } FROM products WHERE id = "${id}";`
} satisfies Config
```

> `sqlQuery` is a custom setting, see <Link href="#example-sql" doNotInferSectionTitle />.

If you define a <Link href="#api">`cumulative` config</Link>, then also make sure to use the global interface `Vike.ConfigResolved`.

```ts ts-only
// /pages/+config.ts
// Environment: config

import type { Config } from 'vike/types'

export default {
  meta: {
    viewport: {
      env: { server: true }
    }
  }
} satisfies Config

declare global {
  namespace Vike {
    interface Config {
      viewport?: number
    }
    interface ConfigResolved {
      // Set the pageContext.config.viewport type
      viewport?: number[]
    }
  }
}
```


## See also

- <Link href="/head-tags#custom-settings" />
- <Link href="/settings" />
